package net.sea.commons.util.zip;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;

import org.apache.log4j.Logger;
import org.apache.tools.zip.ZipEntry;

import java.util.zip.ZipException;

import org.apache.tools.zip.ZipFile;
import org.apache.tools.zip.ZipOutputStream;

import net.sea.commons.util.InstallMsgUtils;

/**
 * ECZipper is an utility for processing compressed file.
 *
 * <p>
 * User: ScottWang
 * </p>
 * <p>
 * Date: 2004-9-2
 * </p>
 * <p>
 * Time: 16:55:18
 * </p>
 */
public class ECZipper {
	@SuppressWarnings("unused")
	private static Logger logger = Logger.getLogger(ECZipper.class);
	private static long totalLength = 0;
	private static long finishLength = 0;

	public final static String unZip(String zipFile, String destDir,
			boolean createRootDir) throws Exception {
		return unZip(zipFile, destDir, createRootDir,
				new ZipEntryIncludeFilter[] { null },
				new ZipEntryExcludeFilter[] { null });
	}

	/**
	 * 清空
	 */
	public static void clear() {
		totalLength = 0;
		finishLength = 0;
	}

	/**
	 * Decompress zip file to a directory.
	 *
	 * @param zipFile
	 *            zip file absoutled path.
	 * @param destDir
	 *            decompress's destination directory.
	 * @param createRootDir
	 *            whether to create 'zip file name' dir.
	 * @param includefilter
	 *            包含过滤器列表，只有有一个过滤器接受，则接收
	 * @param excludeFilter
	 *            排除列表,必须所有过滤器接受，才接受；只要有一个过滤器不接受，则不接受。
	 * @return return decompress's destionation directory.
	 * @throws Exception
	 */
	public final static String unZip(String zipFile, String destDir,
			boolean createRootDir, ZipEntryIncludeFilter includefilter,
			ZipEntryExcludeFilter excludeFilter) throws Exception {
		return unZip(zipFile, destDir, createRootDir,
				new ZipEntryIncludeFilter[] { includefilter },
				new ZipEntryExcludeFilter[] { excludeFilter });
	}

	/**
	 * Decompress zip file to a directory.
	 *
	 * @param zipFile
	 *            zip file absoutled path.
	 * @param destDir
	 *            decompress's destination directory.
	 * @param createRootDir
	 *            whether to create 'zip file name' dir.
	 * @param includefilters
	 *            包含过滤器列表，只有有一个过滤器接受，则接收
	 * @param excludeFilters
	 *            排除列表,必须所有过滤器接受，才接受；只要有一个过滤器不接受，则不接受。
	 * @return return decompress's destionation directory.
	 * @throws Exception
	 */
	public final static String unZip(String zipFile, String destDir,
			boolean createRootDir, ZipEntryIncludeFilter[] includefilters,
			ZipEntryExcludeFilter[] excludeFilters) throws Exception {
		String dir = destDir;
		if (createRootDir)
			dir = dir + "/" + FileSystemManager.getFileName(zipFile, false);
		if (!FileSystemManager.fileExisted(dir)) {
			if (!FileSystemManager.makeDir(dir)) {
				throw new Exception("make directory '" + dir + "' fail!");
			}
		}

		final ZipFile file = new ZipFile(zipFile);

		Enumeration<?> es = file.getEntries();
		while (es.hasMoreElements()) {
			ZipEntry e = (ZipEntry) es.nextElement();
			if(e.isDirectory()){
				continue;
			}
			totalLength += e.getSize();
		}
		es = file.getEntries();
		try {
			// long begin = System.currentTimeMillis();
			outer: while (es.hasMoreElements()) {
				final ZipEntry e = (ZipEntry) es.nextElement();
				if (e.isDirectory()) {
					String newDir = dir + "/" + e.getName();
					File subDir = new File(newDir);
					if (!subDir.exists())
						// subDir.mkdir(); // bug 17560，压缩文件中的空目录解压后没有创建出来
						subDir.mkdirs();
					continue;
				}

				String name = e.getName();
				name = name.replace('\\', '/');
//				logger.info(name);
//				logger.info(new String(name.getBytes(),"GBK"));
				// 设置安装信息
				InstallMsgUtils.setInstallMsg("正在抽取文件："
						+ name.substring(name.lastIndexOf("/") + 1));

				if (excludeFilters != null) {
					for (int i = 0; i < excludeFilters.length; i++) {
						if (excludeFilters[i] == null)
							continue;

						if (!excludeFilters[i].accept(null, name))
							continue outer;
					}
				}

				if (includefilters != null) {
					boolean accepted = false;
					boolean hasFilter = false;
					for (int i = 0; i < includefilters.length; i++) {
						if (includefilters[i] == null)
							continue;
						else if (!hasFilter)
							hasFilter = true;

						if (includefilters[i].accept(null, name)) {
							accepted = true;
							break;
						}
					}
					if (hasFilter && !accepted)
						continue outer;
				}

				final String dest = dir + "/" + name;
				if (!FileSystemManager.fileExisted(dest))
					if (!FileSystemManager.newFile(dest))
						System.out.println("create file '" + dest + "' fail!");
				entryToFile(file, e, dest);
			}

			// log.debug("unZip '" + zipFile + "' to directory '" + destDir +
			// "' " + (System.currentTimeMillis() - begin)
			// + " millisecond has elapsed");
		} catch (Exception e) {
			throw e;
		} finally {
			file.close();
		}
		return dir;
	}

	private final static void entryToFile(ZipFile zip, ZipEntry e, String f)
			throws Exception {
		try {
			InputStream in = zip.getInputStream(e);
			if (in == null) {
				System.out.println("input stream of entry '" + e.getName()
						+ "' is null!");
				return;
			}
			FileOutputStream out = new FileOutputStream(f);

			byte[] bs = new byte[1024];
			int count = 0;
			while ((count = in.read(bs)) != -1) {
				out.write(bs, 0, count);
				finishLength += count;
				// 设置安装进度
				int value = (int) (finishLength * 100 / totalLength);
				InstallMsgUtils.setInstallProcess(value > 100 ? 100 : value);
			}
			out.flush();
			in.close();
			out.close();
		} catch (IOException ie) {
			ie.printStackTrace();
			throw ie;
		}
	}

	/**
	 * 压缩目录到文件
	 *
	 * @param dir
	 *            directory.
	 * @param zipFile
	 *            absoluted path of destination zip file.
	 * @param append
	 *            是否增加文件到zip文件
	 * @return zip file.
	 * @throws Exception
	 */
	public final static String zip(String dir, String zipFile, boolean append)
			throws Exception {
		return zip(dir, zipFile, new FileIncludeFilter[0], null, append);
	}

	/**
	 * 压缩目录到文件
	 *
	 * @param dir
	 * @param zipFile
	 * @param includeFilter
	 * @param excludeFilter
	 * @param append
	 * @return
	 * @throws Exception
	 */
	public final static String zip(String dir, String zipFile,
			FileIncludeFilter includeFilter, FileExcludeFilter excludeFilter,
			boolean append) throws Exception {
		return zip(dir, zipFile, new FileIncludeFilter[] { includeFilter },
				new FileExcludeFilter[] { excludeFilter }, append);
	}

	/**
	 * 压缩目录到文件
	 *
	 * @param dir
	 *            directory.
	 * @param zipFile
	 *            absoluted path of destination zip file.
	 * @param includeFilters
	 *            包含过滤器
	 * @param excludeFilters
	 *            排除过滤器
	 * @param append
	 *            是否添加新的文件到zip文件
	 * @return zip file.
	 * @throws Exception
	 */
	public final static String zip(String dir, String zipFile,
			FileIncludeFilter[] includeFilters,
			FileExcludeFilter[] excludeFilters, boolean append)
			throws Exception {
		File outFile = new File(zipFile);
		if (!outFile.getParentFile().exists())
			outFile.getParentFile().mkdirs();

		ZipOutputStream out = null;
		ZipFile zip = null;

		// long begin = System.currentTimeMillis();

		Set<String> overwriteFileEntries = new HashSet<String>(); // append=true时存放先压缩进去的文件

		if (!new File(zipFile).exists())
			append = false;

		if (append) {
			outFile = new File(new File(zipFile).getParentFile()
					.getAbsolutePath() + "/" + new Date().getTime() + ".zip");
		}

		// 先压缩已有的文件
		out = new ZipOutputStream(new FileOutputStream(outFile));
		File[] fs = FileSystemManager.listDirectory(dir);
		boolean hasExcludeFilter = false;
		if (excludeFilters != null && excludeFilters.length > 0) {
			for (FileIncludeFilter filter : excludeFilters) {
				if (filter != null) {
					hasExcludeFilter = true;
					break;
				}
			}
		}

		boolean hasIncludeFilter = false;
		if (includeFilters != null && includeFilters.length > 0) {
			for (FileIncludeFilter filter : includeFilters) {
				if (filter != null) {
					hasIncludeFilter = true;
					break;
				}
			}
		}

		for (int i = 0; i < fs.length; i++) {
			File curF = fs[i];

			if (hasExcludeFilter) {
				boolean accepted = true;
				for (FileExcludeFilter filter : excludeFilters) {
					if (!filter.accept(curF)) {
						accepted = false;
						break;
					}
				}
				if (!accepted)
					continue;
			}

			if (hasIncludeFilter) {
				boolean accepted = false;
				for (FileIncludeFilter filter : includeFilters) {
					if (filter == null)
						continue;
					if (filter.accept(curF)) {
						accepted = true;
						break;
					}
				}

				if (!accepted)
					continue;
			}

			writeFileToZip(dir, curF, out, append, overwriteFileEntries);
		}

		if (append) { // 在压缩原来zip中的文件
			zip = new ZipFile(zipFile);
			Enumeration<?> entries = zip.getEntries();
			while (entries.hasMoreElements()) {
				ZipEntry entry = (ZipEntry) entries.nextElement();

				if (append && overwriteFileEntries.contains(entry.getName()))
					continue;

				out.putNextEntry(entry);

				InputStream in = zip.getInputStream(entry);

				byte[] bs = new byte[1024];
				int len = 0;
				while ((len = in.read(bs)) != -1) {
					out.write(bs, 0, len);
				}
				out.flush();
				out.closeEntry();
			}
			zip.close();
		}

		out.finish();
		out.close();

		if (append) {
			new File(zipFile).delete();
			outFile.renameTo(new File(zipFile));
		}
		// log.debug("Zip to '" + zipFile + "' from directory '" + dir + "' " +
		// (System.currentTimeMillis() - begin)
		// + " millisecond has elapsed");
		return zipFile;
	}

	private static void writeFileToZip(String dir, File file,
			ZipOutputStream out, boolean append, Set<String> fileEntries)
			throws Exception {
		FileInputStream in = null;
		try {
			String name = FileSystemManager.getRelativePath(dir,
					file.getAbsolutePath());
			if (name.startsWith("/"))
				name = name.substring(1);
			if (file.isFile() && (name.endsWith("/") || name.endsWith("\\"))) {
				name = name.substring(0, name.length() - 1);
			}
			if (append)
				fileEntries.add(name);
			ZipEntry entry = new ZipEntry(name);
			out.putNextEntry(entry);
			if (!file.isDirectory()) {
				in = new FileInputStream(file);
				byte[] bs = new byte[1024];
				int len = 0;
				while ((len = in.read(bs)) != -1) {
					out.write(bs, 0, len);
				}
				out.flush();
			}
		} catch (ZipException e) {
			if (!file.isDirectory())
				// log.error(e);
				e.printStackTrace();
		} catch (Exception e) {
			// log.error(e);
			e.printStackTrace();
			throw e;
		} finally {
			if (in != null)
				in.close();
			out.closeEntry();
		}
	}

}

class UnZipProcessor extends Thread {
	static class Task {
		public static ZipFile zipF;
		private static int ref;
		public ZipEntry curE;
		public String destF;

		public static synchronized void incRef() {
			ref++;
		}

		public static synchronized void decRef() {
			ref--;
		}

		public static synchronized void close() {
			if (ref == 0) {
				try {
					zipF.close();
				} catch (IOException ie) {
					ie.printStackTrace();
				}
			}
		}
	}

	private Stack<Task> processQueue = new Stack<Task>();

	public UnZipProcessor(ZipFile zip) {
		Task.zipF = zip;
	}

	public void push(ZipEntry e, String dest) {
		Task t = new Task();
		t.curE = e;
		t.destF = dest;
		processQueue.push(t);
		Task.incRef();
	}

	public void run() {
		while (processQueue.size() > 0) {
			Task curT = (Task) processQueue.pop();
			Task.decRef();
			entryToFile(Task.zipF, curT.curE, curT.destF);
		}

		Task.close();
	}

	private final static void entryToFile(ZipFile zip, ZipEntry e, String f) {
		try {
			InputStream in = zip.getInputStream(e);
			FileOutputStream out = new FileOutputStream(f);

			byte[] bs = new byte[1024];
			int count = 0;
			while ((count = in.read(bs)) != -1) {
				out.write(bs, 0, count);
			}

			in.close();
			out.close();
		} catch (IOException ie) {
			ie.printStackTrace();
		}
	}

}